/**************************************************************************************
*
*	Copyright (C) 2009 - 2012 Brent Andrew O. Chua - See LICENSE.txt for more info.
*		
*	Filename	-	Scene.cpp
*
*	Comments	-	See Scene.h
*
**************************************************************************************/
#include "../Include/Scene.h"
#include "../Include/Actor.h"
#include "../Include/ComponentCamera.h"
#include "../Include/Engine.h"
#include "../Include/Renderer.h"
#include "../Include/Logic.h"
#include "../Include/WindowView.h"
#include "../Include/PhysicsSystem.h"
#include "../Include/FactoryActor.h"

#include "../Include/EventNewActiveCamera.h"

namespace Pulse
{
	Scene::Scene( void )
	{
		m_fixedTimeAccumulated = 0.0f;

		m_pViewOwner	= PSX_NULL;
		m_pRoot			= PSX_NULL;
		m_pActiveCamera = PSX_NULL;
		
		m_pPhysics = PSX_NULL;

		m_pFactoryActor = PSX_NULL;
	}

	Scene::~Scene( void )
	{
		Cleanup();
	}

	void Scene::FixedUpdate( const FLOAT dt )
	{
	}

	void Scene::Update( const FLOAT dt )
	{
		// Fixed update
		{
			const FLOAT m_fixedTimeStep = 0.02f;
			const FLOAT m_fixedMaxTimeStep = 0.3333333f;
			FLOAT fixedDT = dt;

			// Check to avoid "Spiral of Death"
			if ( fixedDT > m_fixedMaxTimeStep )
			{
				fixedDT = m_fixedMaxTimeStep;
			}

			m_fixedTimeAccumulated += fixedDT;

			while ( m_fixedTimeAccumulated >= m_fixedTimeStep )
			{
				m_fixedTimeAccumulated -= m_fixedTimeStep;
				//m_pRoot->FixedUpdate( fixedDT );
			}
		}

		// Physics
		m_pPhysics->Update( dt );
		
		// Update and sync all actors and component datas
		m_pRoot->Update( dt );
		
		// Process dispatched data
		m_eventSystem.ProcessEvents();

		ProcessDeferreActorDestroy();
	}

	void Scene::Render( void )
	{
		if ( m_pRoot && m_pActiveCamera )
		{
			ComponentCamera *pCamera = (ComponentCamera*)m_pActiveCamera->GetComponent( EComponent::CAMERA );

			// then scene
			pCamera->Render( this );
		}
	}

	void Scene::Render( ComponentCamera *pCamera, RenderView *pView )
	{
		if ( m_pRoot && pCamera && pView )
		{
			m_pRoot->Render( this, pCamera, pView );
		}
	}

	void Scene::SetCamera( ActorPtr pCamera )
	{
		if ( pCamera )
		{
			ComponentCamera *pCameraComponent = (ComponentCamera*)pCamera->GetComponent( EComponent::CAMERA );
			
			if ( pCameraComponent  )
			{
				if ( m_pActiveCamera )
				{
					ComponentCamera *pOldComponent = (ComponentCamera*)m_pActiveCamera->GetComponent( EComponent::CAMERA );
					pOldComponent->SetActive( FALSE );
				}

				// TODO: Make sure the camera we're adding should be a member of this scene...

				ActorPtr oldActiveCam = m_pActiveCamera;
				m_pActiveCamera = pCamera;
				pCameraComponent->SetActive( TRUE );

				// Fire event to let everyone know we have a new active cam set
				EventNewActiveCamera event( pCamera, oldActiveCam );
				TriggerEvent( &event );
			}
			else
			{
				m_pActiveCamera = PSX_NULL;

				// Fire event to let everyone know we have a new active cam set
				EventNewActiveCamera event( PSX_NULL, PSX_NULL );
				TriggerEvent( &event );
			}
		}
		else
		{
			m_pActiveCamera = PSX_NULL;

			// Fire event to let everyone know we have a new active cam set
			EventNewActiveCamera event( PSX_NULL, PSX_NULL );
			TriggerEvent( &event );
		}
	}

	void Scene::InsertActor( ActorPtr pActor )
	{
		pActor->SetScene( this );

		m_pRoot->AttachChild( pActor );
	}

	void Scene::QueueActorDestroy( ActorPtr pActor )
	{
		m_deferredDestroyActors.PushBack( pActor );
	}

	void Scene::QueueComponentDestroy( IComponent *pComponent )
	{
		m_deferredDestroyComponents.PushBack( pComponent );
	}

	ActorPtr Scene::NewActor( void )
	{
		ActorPtr pNewActor = m_pFactoryActor->NewActor();

		InsertActor( pNewActor );

		return pNewActor;
	}

	EErrorCode::Type Scene::Save( const CHAR *pFilename )
	{

		return EErrorCode::OKAY;
	}

	void Scene::TriggerEvent( IEvent *pEvent )
	{
		m_eventSystem.TriggerEvent( pEvent );
	}

	void Scene::PostEvent( EventPtr pEvent )
	{
		m_eventSystem.PostEvent( pEvent );
	}

	void Scene::ProcessDeferredComponentDestroy( void )
	{
		while ( m_deferredDestroyComponents.IsEmpty() == FALSE )
		{
			IComponent *pComponent = m_deferredDestroyComponents.GetFront();
			m_deferredDestroyComponents.PopFront();

			Actor *pActor = pComponent->GetOwnerUnsafe();

			if ( pActor->RemoveComponent( pComponent->GetType() ) == FALSE )
			{
				PSX_PushError( "Failed to remove component." );
			}
		}
	}

	void Scene::ProcessDeferreActorDestroy( void )
	{
		// Process deferred actor destroy
		while ( m_deferredDestroyActors.IsEmpty() == FALSE )
		{
			ActorPtr pActor = m_deferredDestroyActors.GetFront();
			m_deferredDestroyActors.PopFront();

			m_pFactoryActor->RemoveActor( pActor );

			// pActor gets auto-delete
		}
	}

	EErrorCode::Type Scene::Initialize( WindowView *pOwner )
	{
		if ( pOwner == PSX_NULL )
		{
			PSX_PushError( "pOwner is invalid." );
			return EErrorCode::INVALID_PARAMETER;
		}

		if ( m_pRoot )
		{
			PSX_PushWarning( "Already initialized." );
			return EErrorCode::OKAY;
		}

		m_pViewOwner = pOwner;

		// Systems Initialization
		m_pFactoryActor = new FactoryActor;

		if ( m_pFactoryActor == PSX_NULL )
		{
			PSX_PushError( "Failed to create actor factory." );
			return EErrorCode::_ERROR;
		}

		m_pPhysics = CreatePhysicsSystem();

		if ( m_pPhysics == PSX_NULL )
		{
			PSX_PushError( "Failed to create scene physics system." );
			return EErrorCode::_ERROR;
		}

		EErrorCode::Type retCode; 
		if ( (retCode = m_pPhysics->Initialize()) != EErrorCode::OKAY )
		{
			PSX_PushError( "Failed to initialize scene physics system." );
			return retCode;
		}

		// Scene Initialization
		{
			FactoryActor *pFactory = m_pFactoryActor;
			m_pRoot = pFactory->NewActor();
			m_pRoot->SetScene( this );
		}

		// Default scene always have a camera
		ActorPtr pCamera = NewActor();
		ComponentCamera *pCameraComponent = (ComponentCamera*)pCamera->AddComponent( EComponent::CAMERA );
		
		// Set camera settings
		FLOAT aspect = (FLOAT)pOwner->GetWidth() / (FLOAT)pOwner->GetHeight();	
		aspect = 683.0f/384.0f; // HACK
		pCameraComponent->SetProjectionPerspective( 0.01f, 1000.0f, aspect, PSX_PI * 0.25f );

		pCamera->SetPosition( 0.0f, 0.0f, -5.0f ); // HACK

		SetCamera( pCamera );
		InsertActor( pCamera );

		return EErrorCode::OKAY;
	}

	void Scene::Cleanup( void )
	{
		// Remove scene to be queued in the deffered destroy
		m_pRoot->Destroy();

		// Remove all reference
		m_pActiveCamera = PSX_NULL;
		m_pRoot			= PSX_NULL;

		// Force destroy all components
		ProcessDeferredComponentDestroy();

		// Force destroy all actors
		ProcessDeferreActorDestroy();

		m_pViewOwner	= PSX_NULL;

		// Then systems
		DestroyPhysicsSystem( m_pPhysics ); m_pPhysics = PSX_NULL;
		PSX_SafeDelete( m_pFactoryActor );
	}
}
